/**
 * @file log_utils.c
 * @brief 统一日志系统实现
 * 
 * 实现统一的日志记录功能，支持不同日志级别、控制台输出。
 * 需要写入文件的日志通过UDP套接字发送到日志服务线程。
 * 支持智能检测后台运行，避免无意义的控制台输出。
 * 
 * @author 伞控无人机项目组
 * @date 2024
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log_utils.h"

// 默认日志级别
static log_level_t g_log_level = LOG_LEVEL_INFO;

// 控制台输出控制
static int g_console_mode = LOG_CONSOLE_AUTO;
static int g_console_enabled = 1;  // 实际的控制台输出开关
static int g_console_detected = 0; // 是否已检测过控制台状态

// UDP日志发送配置
static int g_udp_socket = -1;
static struct sockaddr_in g_udp_addr;
static pthread_mutex_t g_udp_mutex = PTHREAD_MUTEX_INITIALIZER;

// 日志级别对应的名称
static const char *g_log_level_names[] = {
    "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"
};

// 日志级别对应的颜色
static const char *g_log_level_colors[] = {
    LOG_COLOR_FATAL, LOG_COLOR_ERROR, LOG_COLOR_WARN, 
    LOG_COLOR_INFO, LOG_COLOR_DEBUG, LOG_COLOR_TRACE
};

// 内部函数声明
static const char *get_filename_from_path(const char *path);
static int init_udp_client(void);
static int send_log_via_udp(const char *log_data);
static void detect_console_status(void);
static int should_output_to_console(void);

/**
 * @brief 检测控制台状态
 */
static void detect_console_status(void) {
    if (g_console_detected) {
        return; // 已经检测过了
    }
    
    // 检测stdout是否连接到终端
    int is_tty = isatty(STDOUT_FILENO);
    
    // 检测是否在后台运行（通过检查进程组ID）
    pid_t pgid = getpgrp();
    pid_t tcpgid = tcgetpgrp(STDOUT_FILENO);
    int is_background = (tcpgid != pgid && tcpgid != -1);
    
    // 根据检测结果决定是否启用控制台输出
    if (g_console_mode == LOG_CONSOLE_AUTO) {
        // 自动模式：只有在前台运行且连接到终端时才输出
        g_console_enabled = (is_tty && !is_background);
        
        // 输出检测结果（使用简单的printf，避免递归调用）
        if (g_console_enabled) {
            printf("[LOG] 检测到前台终端运行，启用控制台输出\n");
        } else {
            // 即使在后台，也输出一次检测结果到可能的日志文件
            printf("[LOG] 检测到后台运行或非终端环境，禁用控制台输出以提升性能\n");
        }
    } else if (g_console_mode == LOG_CONSOLE_FORCE_ON) {
        g_console_enabled = 1;
        printf("[LOG] 强制启用控制台输出\n");
    } else if (g_console_mode == LOG_CONSOLE_FORCE_OFF) {
        g_console_enabled = 0;
        printf("[LOG] 强制禁用控制台输出\n");
    }
    
    g_console_detected = 1;
}

/**
 * @brief 判断是否应该输出到控制台
 */
static int should_output_to_console(void) {
    if (!g_console_detected) {
        detect_console_status();
    }
    return g_console_enabled;
}

/**
 * @brief 初始化日志系统
 * 
 * @return int 0-成功，-1-失败
 */
int log_init(void) {
    // 初始化UDP客户端
    if (init_udp_client() != 0) {
        fprintf(stderr, "初始化UDP日志客户端失败\n");
        return -1;
    }
    
    // 检测控制台状态
    detect_console_status();
    
    // 使用文件输出记录初始化信息
    log_message(LOG_TO_FILE, LOG_LEVEL_INFO, LOG_MODULE_LOG, __FILE__, __LINE__, __func__, 
               "日志系统初始化成功，日志级别: %s", g_log_level_names[g_log_level]);
    
    return 0;
}

/**
 * @brief 设置日志级别
 * 
 * @param level 日志级别
 */
void log_set_level(log_level_t level) {
    if (level <= LOG_LEVEL_TRACE) {
        g_log_level = level;
        log_message(LOG_TO_FILE, LOG_LEVEL_INFO, LOG_MODULE_LOG, __FILE__, __LINE__, __func__, 
                   "日志级别已设置为: %s", g_log_level_names[g_log_level]);
    }
}

/**
 * @brief 获取当前日志级别
 * 
 * @return log_level_t 当前日志级别
 */
log_level_t log_get_level(void) {
    return g_log_level;
}

/**
 * @brief 设置控制台输出模式
 * 
 * @param mode 控制台输出模式
 */
void log_set_console_mode(int mode) {
    if (mode >= LOG_CONSOLE_AUTO && mode <= LOG_CONSOLE_FORCE_OFF) {
        g_console_mode = mode;
        g_console_detected = 0; // 重新检测
        
        // 立即检测新的状态
        detect_console_status();
        
        log_message(LOG_TO_FILE, LOG_LEVEL_INFO, LOG_MODULE_LOG, __FILE__, __LINE__, __func__, 
                   "控制台输出模式已设置为: %d", mode);
    }
}

/**
 * @brief 获取当前控制台输出模式
 * 
 * @return int 当前控制台输出模式
 */
int log_get_console_mode(void) {
    return g_console_mode;
}

/**
 * @brief 记录日志信息
 * 
 * @param to_file 是否发送到日志服务线程，1表示发送，0表示仅控制台输出
 * @param level 日志级别
 * @param module 模块名称
 * @param file 源文件名
 * @param line 行号
 * @param func 函数名
 * @param format 格式化字符串
 * @param ... 可变参数
 */
void log_message(int to_file, log_level_t level, const char *module, const char *file, 
                int line, const char *func, const char *format, ...) {
    // 检查日志级别
    if (level > g_log_level) {
        return;
    }
    
    // 获取当前时间
    struct timeval tv;
    gettimeofday(&tv, NULL);
    
    struct tm *tm_info = localtime(&tv.tv_sec);
    
    char time_str[32];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
    
    // 处理模块名称
    char module_name[LOG_MODULE_NAME_MAX_LEN + 1] = {0};
    if (module != NULL) {
        strncpy(module_name, module, LOG_MODULE_NAME_MAX_LEN);
        module_name[LOG_MODULE_NAME_MAX_LEN] = '\0';
    } else {
        strcpy(module_name, "UNKNOWN");
    }
    
    // 获取线程ID
    pthread_t tid = pthread_self();
    
    // 获取文件名（不包含路径）
    const char *filename = get_filename_from_path(file);
    
    // 格式化日志消息
    va_list args;
    va_start(args, format);
    
    char msg_buffer[2048];
    vsnprintf(msg_buffer, sizeof(msg_buffer), format, args);
    
    va_end(args);
    
    // 构建完整的日志行（用于文件写入）
    char log_line[4096];
    snprintf(log_line, sizeof(log_line), 
             "%s.%03ld [%s] [%s] [%lu] [%s:%d %s] %s\n",
             time_str, tv.tv_usec / 1000,
             g_log_level_names[level],
             module_name,
             (unsigned long)tid,
             filename, line, func,
             msg_buffer);
    
    // 根据检测结果决定是否输出到控制台
    if (should_output_to_console()) {
        // 构建带颜色的控制台输出
        char console_line[4096];
        snprintf(console_line, sizeof(console_line), 
                 "%s%s.%03ld [%s] [%s] [%lu] [%s:%d %s] %s%s\n",
                 g_log_level_colors[level],
                 time_str, tv.tv_usec / 1000,
                 g_log_level_names[level],
                 module_name,
                 (unsigned long)tid,
                 filename, line, func,
                 msg_buffer,
                 LOG_COLOR_RESET);
        
        // 输出到控制台
        fputs(console_line, stdout);
        
        // 只在交互式终端中强制刷新，提升性能
        if (isatty(STDOUT_FILENO)) {
            fflush(stdout);
        }
    }
    
    // 根据to_file参数决定是否发送到日志服务线程
    if (to_file == LOG_TO_FILE) {
        // 通过UDP发送到日志服务器
        send_log_via_udp(log_line);
    }
}

/**
 * @brief 清理日志系统资源
 */
void log_cleanup(void) {
    // 清理UDP socket
    pthread_mutex_lock(&g_udp_mutex);
    if (g_udp_socket >= 0) {
        close(g_udp_socket);
        g_udp_socket = -1;
    }
    pthread_mutex_unlock(&g_udp_mutex);
    
    // 使用文件输出记录日志系统清理信息
    log_message(LOG_TO_FILE, LOG_LEVEL_INFO, LOG_MODULE_LOG, __FILE__, __LINE__, __func__, 
               "日志系统已清理");
}

/**
 * @brief 从路径中提取文件名
 * 
 * @param path 文件路径
 * @return const char* 文件名
 */
static const char *get_filename_from_path(const char *path) {
    if (path == NULL) return "unknown";
    
    const char *filename = strrchr(path, '/');
    if (filename == NULL) {
        filename = path;
    } else {
        filename++;  // 跳过'/'字符
    }
    
    return filename;
}

/**
 * @brief 初始化UDP客户端
 * 
 * @return int 0-成功，-1-失败
 */
static int init_udp_client(void) {
    pthread_mutex_lock(&g_udp_mutex);
    
    if (g_udp_socket >= 0) {
        pthread_mutex_unlock(&g_udp_mutex);
        return 0; // 已经初始化
    }
    
    // 创建UDP socket
    g_udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (g_udp_socket < 0) {
        pthread_mutex_unlock(&g_udp_mutex);
        return -1;
    }
    
    // 设置缓冲区大小
    int buffer_size = LOG_UDP_BUFFER_SIZE;
    setsockopt(g_udp_socket, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
    
    // 配置服务器地址
    memset(&g_udp_addr, 0, sizeof(g_udp_addr));
    g_udp_addr.sin_family = AF_INET;
    g_udp_addr.sin_port = htons(LOG_UDP_PORT);
    g_udp_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    pthread_mutex_unlock(&g_udp_mutex);
    return 0;
}

/**
 * @brief 通过UDP发送日志数据
 * 
 * @param log_data 日志数据
 * @return int 0-成功，-1-失败
 */
static int send_log_via_udp(const char *log_data) {
    if (log_data == NULL) {
        return -1;
    }
    
    pthread_mutex_lock(&g_udp_mutex);
    
    // 检查UDP客户端是否已初始化
    if (g_udp_socket < 0) {
        pthread_mutex_unlock(&g_udp_mutex);
        return -1;
    }
    
    // 发送数据，失败就丢弃（不阻塞主程序）
    ssize_t sent = sendto(g_udp_socket, log_data, strlen(log_data), 0, 
                         (struct sockaddr*)&g_udp_addr, sizeof(g_udp_addr));
    
    pthread_mutex_unlock(&g_udp_mutex);
    
    // 返回发送结果，但不影响主程序运行
    return (sent > 0) ? 0 : -1;
} 